/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ie.tcd.cs.railtickets.agent.graph;

import ie.tcd.cs.railtickets.agent.config.ConfigProperties;
import ie.tcd.cs.railtickets.agent.service.RequestService;
import ie.tcd.cs.railtickets.agent.service.ServiceFactory;
import ie.tcd.cs.railtickets.commons.util.CacheUtil;
import ie.tcd.cs.railtickets.commons.util.FileUtil;
import ie.tcd.cs.railtickets.commons.util.PropertiesUtil;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jgrapht.Graph;
import org.jgrapht.GraphPath;
import org.jgrapht.Graphs;
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.alg.KShortestPaths;

/**
 *
 * @author Wenbo Sun
 */
public class PathGraph {

    private RequestService request;
    private static PathGraph instance;
    private static String GRAPHMAP = "graph/graphmap.jo";
    private static String MAPKEY = "graphmap";
    private static String INTEGRATEDKEY = "integrated";
    private static String INTEGRATEDGRAPH = "graph/integrate.jo";

    private static final Log log = LogFactory.getLog(PathGraph.class);

    public PathGraph() {
        try {
            request = ServiceFactory.createRequestService();
            if (!FileUtil.isExisted(GRAPHMAP) || !FileUtil.isExisted(INTEGRATEDGRAPH)) {
                this.updateGraphs();
            }
            CacheUtil.getInstance().store(MAPKEY, FileUtil.file2Object(GRAPHMAP));
            CacheUtil.getInstance().store(INTEGRATEDKEY, FileUtil.file2Object(INTEGRATEDGRAPH));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(PathGraph.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(PathGraph.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(PathGraph.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static PathGraph getInstance() {
        synchronized (PathGraph.class) {
            if (instance == null) {
                instance = new PathGraph();
            }
        }
        return instance;
    }

    public Graph<String, DefaultEdge> getPathGraph(String target) {
        return request.getRailsGraphRequest(target);
    }

    public Map<String, Graph<String, DefaultEdge>> getAllGraphs() {
        Set<String> operators = getAllOperator();
        Map<String, Graph<String, DefaultEdge>> graphsMap = new HashMap<String, Graph<String, DefaultEdge>>();
        for (String operator : operators) {
            Graph graph = request.getRailsGraphRequest(operator);
            graphsMap.put(operator, graph);
        }

        return graphsMap;
    }

    public Graph<String, DefaultEdge> integrateGraphs(Map<String, Graph<String, DefaultEdge>> graphsMap) {
        Graph<String, DefaultEdge> graph = new DefaultDirectedWeightedGraph<String, DefaultEdge>(DefaultEdge.class);
        for (Graph g : graphsMap.values()) {
            Graphs.addGraph(graph, g);
        }
        return graph;
    }

    public Set<String> getAllOperator() {
        return ConfigProperties.getInstance().getProperties().stringPropertyNames();
    }

    public List<GraphPath<String, DefaultEdge>> getPath(Graph<String, DefaultEdge> graph, String source, String end) {
        Properties pros = null;
        try {
            pros = PropertiesUtil.getProperties("config/paths.properties");
        } catch (IOException ex) {
            Logger.getLogger(PathGraph.class.getName()).log(Level.SEVERE, null, ex);
        }
        int nPaths = Integer.parseInt(pros.getProperty("nPaths"));
        int nMaxHops = Integer.parseInt(pros.getProperty("nMaxHops"));
        KShortestPaths<String, DefaultEdge> ksp = new KShortestPaths<String, DefaultEdge>(graph, source, nPaths, nMaxHops);
        log.debug(end);
        List<GraphPath<String, DefaultEdge>> list = ksp.getPaths(end);
        
        return list;
    }

    public void updateGraphs() {
        try {
            Map<String, Graph<String, DefaultEdge>> graphMap = this.getAllGraphs();
            log.debug("update " + graphMap.size() + " graphs");
            FileUtil.saveObject(GRAPHMAP, graphMap);
            CacheUtil.getInstance().store(MAPKEY, graphMap);
            Graph<String, DefaultEdge> integratedGraph = this.integrateGraphs(graphMap);
            FileUtil.saveObject(INTEGRATEDGRAPH, integratedGraph);
            CacheUtil.getInstance().store(INTEGRATEDKEY, integratedGraph);

        } catch (FileNotFoundException ex) {
            Logger.getLogger(PathGraph.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(PathGraph.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public List<String[]> searchPaths(List<DefaultEdge> edges ) {
        Graph<String, DefaultEdge> graph = (Graph<String, DefaultEdge>) CacheUtil.getInstance().retreive(INTEGRATEDKEY);
        CacheUtil.getInstance().store("edges", edges);
        List<String[]> list = new ArrayList<String[]>();
        if (edges == null) {
            return null;
        }
        for (DefaultEdge edge : edges) {
            String[] s = new String[2];
            s[0] = graph.getEdgeSource(edge);
            s[1] = graph.getEdgeTarget(edge);
            list.add(s);
        }
        return list;
    }

    public List<String> getTargetOperators(String[] verts) {
        Map<String, Graph<String, DefaultEdge>> map = (Map<String, Graph<String, DefaultEdge>>) CacheUtil.getInstance().retreive(MAPKEY);
        log.debug(map.size() + " graphs in the map");
        List<String> list = new ArrayList<String>();
        for (String key : map.keySet()) {
            Graph<String, DefaultEdge> graph = map.get(key);
            if (graph.containsEdge(verts[0], verts[1])) {
                list.add(key);
            }
        }
        log.debug("get target operators: " + list.size());
        return list;
    }

    public String[] getVerts(DefaultEdge edge) {
        Graph<String, DefaultEdge> graph = (Graph<String, DefaultEdge>) CacheUtil.getInstance().retreive(INTEGRATEDKEY);
        String[] s = new String[2];
        s[0] = graph.getEdgeSource(edge);
        s[1] = graph.getEdgeTarget(edge);
        return s;
    }

    public Graph<String, DefaultEdge> getIntegratedGraph() {
        return (Graph<String, DefaultEdge>) CacheUtil.getInstance().retreive(INTEGRATEDKEY);
    }
}
